using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace DuplexTransformers
{
    public static class DynamicMutator
    {
        public static void InvokeMutator<T, TProp>(Expression<Func<T, TProp>> correspondingTo, T on, TProp value)
        {
            var stack = ResolveAccessorStack(correspondingTo);
            object invokeObj = on;
            var archetype = stack.Pop();
            while (stack.Any())
            {
                var accessor = (PropertyInfo)archetype.Member;
                var parameterExpression = Expression.Parameter(invokeObj.GetType(), "x");
                var memberExpression = Expression.Property(parameterExpression, accessor);
                var @delegate = Expression.Lambda(memberExpression, parameterExpression).Compile();
                invokeObj = @delegate.DynamicInvoke(invokeObj);
                archetype = stack.Pop();
            }

            var member = (PropertyInfo)archetype.Member;
            var setMethod = member.GetSetMethod(true);

            setMethod.Invoke(invokeObj, new object[] { value });
        }

        private static Stack<MemberExpression> ResolveAccessorStack(Expression expression)
        {
            var result = new Stack<MemberExpression>();
            while (expression.NodeType == ExpressionType.Lambda)
            {
                expression = ((LambdaExpression)expression).Body;
            }

            var memberExpression = (MemberExpression)expression;
            result.Push(memberExpression);
            while ((memberExpression = memberExpression.Expression as MemberExpression) != null)
            {
                result.Push(memberExpression);
            }

            return result;
        }
    }
}